#include "FileUpLoad.h"
#include "Sigin.h"
#include "CryptoUtil.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>     
#include <nlohmann/json.hpp>
#include <workflow/MySQLResult.h>
#include <wfrest/PathUtil.h>
#include <map>
#include <string>
#include <sstream>
#include <iostream>
#include <functional>
using std::map;
using std::string;
using std::function;
using std::ostringstream;
using std::bind;
using namespace protocol;
using namespace wfrest;

using JSON = nlohmann::json;

static string LocalRootPath = "./file/";
static string YunRootPath = "disk/";

FileUpLoad::FileUpLoad(wfrest::HttpServer & server)
:m_server(server)
,m_ossManage()
{}

struct FileUpLoadContext{
    HttpResp * resp;
    string hashCode;
    string username;
    string filename;
    string storagepath;
    string content;
    int ufid;
    int fid; 
    bool is_insert;
    bool is_save;
};

void FileUpLoad::fileuploadMysqlCallbackResult(WFMySQLTask * mysqlTask){
    
    FileUpLoadContext * context = static_cast<FileUpLoadContext*>(series_of(mysqlTask)->get_context());
    if(mysqlTask->get_state() !=WFT_STATE_SUCCESS){
        context->resp->String("mysqlTask failed");
        return;
    }

    MySQLResultCursor cursor(mysqlTask->get_resp());

    if(cursor.get_cursor_status()==MYSQL_PACKET_ERROR){
        context->resp->String("error");
        return ;
    }
    #ifdef DEBUG
    std::cout<<"Result\n";
    #endif

    context->resp->Redirect("/static/view/home.html", HttpStatusSeeOther);
}

void FileUpLoad::fileuploadModifyTable(SeriesWork * series,const FileIOArgs *){
    
    function<void(WFMySQLTask *)> func = bind(&FileUpLoad::fileuploadMysqlCallbackResult,this,std::placeholders::_1);
    
    FileUpLoadContext * context = static_cast<FileUpLoadContext*>(series->get_context());
 
    m_ossManage.OSSupload(context->hashCode,context->content);

    ostringstream oss ;
    string sql ;
    if(context->is_insert){
        oss<<"insert into user_file_table (fid,username,filename) values ('"<<context->fid<<"','"<<context->username<<"','"<<context->filename<<"');";        
    }else{
        oss<<"UPDATE user_file_table SET fid = "<<context->fid<<",filename = '"<<context->filename<<"' WHERE id = "<<context->ufid<<";";
    } 
    sql = oss.str();
    WFMySQLTask * mysqltask = WFTaskFactory::create_mysql_task("mysql://root:123@localhost/newCloudDisk",3,func);
    mysqltask->get_req()->set_query(sql);
    series->push_back(mysqltask);
}

void FileUpLoad::fileuploadMysqlCallbackSavefile(WFMySQLTask * mysqlTask){
    
    function<void(const FileIOArgs *)> func = bind(&FileUpLoad::fileuploadModifyTable,this,series_of(mysqlTask),std::placeholders::_1);
    
    FileUpLoadContext * context = static_cast<FileUpLoadContext*>(series_of(mysqlTask)->get_context());
    
    if(mysqlTask->get_state() !=WFT_STATE_SUCCESS){
        context->resp->String("mysqlTask failed");
        return;
    }

    MySQLResultCursor cursor(mysqlTask->get_resp());

    if(cursor.get_cursor_status()==MYSQL_PACKET_ERROR){
        context->resp->String("error");
        return ;
    }

    if(cursor.get_cursor_status()==MYSQL_STATUS_GET_RESULT){
        #ifdef DEBUG
            std::cout<<"saveTask\n";
        #endif
        map<string,MySQLCell> row;
        cursor.fetch_row(row);
        context->fid = row["id"].as_int();

        if(context->is_save){
            context->resp->Save(context->storagepath,context->content,func);  
            
        }else{
            fileuploadModifyTable(series_of(mysqlTask),nullptr);
        }
    }
}

void FileUpLoad::fileuploadMysqlCallbackInsert(WFMySQLTask * mysqlTask){
    
    function<void(WFMySQLTask *)> func = bind(&FileUpLoad::fileuploadMysqlCallbackSavefile,this,std::placeholders::_1);
    
    FileUpLoadContext * context = static_cast<FileUpLoadContext*>(series_of(mysqlTask)->get_context());
    if(mysqlTask->get_state() !=WFT_STATE_SUCCESS){
        context->resp->String("mysqlTask failed");
        return;
    }

    MySQLResultCursor cursor(mysqlTask->get_resp());

    if(cursor.get_cursor_status()==MYSQL_PACKET_ERROR){
        context->resp->String("error");
        return ;
    }
    if(cursor.get_cursor_status()==MYSQL_STATUS_OK){
        ostringstream oss ; 
        string sql;
        oss<<"select * from file_table where hashcode='"<<context->hashCode<<"';";
        sql = oss.str();
        WFMySQLTask * mysqltask = WFTaskFactory::create_mysql_task("mysql://root:123@localhost/newCloudDisk",3,func);
        mysqltask->get_req()->set_query(sql);
        series_of(mysqlTask)->push_back(mysqltask);
    }
}

void FileUpLoad::fileuploadMysqlCallbackUserfile(WFMySQLTask * mysqlTask){

    function<void(WFMySQLTask *)> func1 = bind(&FileUpLoad::fileuploadMysqlCallbackInsert,this,std::placeholders::_1);
    function<void(WFMySQLTask *)> func2 = bind(&FileUpLoad::fileuploadMysqlCallbackSavefile,this,std::placeholders::_1);

    FileUpLoadContext * context = static_cast<FileUpLoadContext*>(series_of(mysqlTask)->get_context());
    if(mysqlTask->get_state() !=WFT_STATE_SUCCESS){
        context->resp->String("mysqlTask failed");
        return;
    }

    MySQLResultCursor cursor(mysqlTask->get_resp());

    if(cursor.get_cursor_status()==MYSQL_PACKET_ERROR){
        context->resp->String("error");
        return ;
    }

    if(cursor.get_cursor_status()==MYSQL_STATUS_GET_RESULT){
        map<string,MySQLCell> row;
        cursor.fetch_row(row);
        int size = (context->content).size();
        string sql ;
        ostringstream oss ; 
        if(row.size()==0){
            context->is_save = true;
            oss<<"insert into file_table (hashcode,size) values ('"<<context->hashCode<<"',"<<size<<");";
            sql = oss.str();
            WFMySQLTask * mysqltask = WFTaskFactory::create_mysql_task("mysql://root:123@localhost/newCloudDisk",3,func1);
            mysqltask->get_req()->set_query(sql);
            series_of(mysqlTask)->push_back(mysqltask);
            #ifdef DEBUG
            std::cout<<"has row size = 0\n";
            std::cout<<oss.str()<<"\n";
            #endif
            return ;
        }else{
            context->is_save = false;
            oss<<"select id from file_table where hashcode = '"<<context->hashCode<<"';";
            sql = oss.str();
            WFMySQLTask * mysqltask = WFTaskFactory::create_mysql_task("mysql://root:123@localhost/newCloudDisk",3,func2);
            mysqltask->get_req()->set_query(sql);
            series_of(mysqlTask)->push_back(mysqltask);
        }
    }
}

void FileUpLoad::fileuploadMysqlCallback(WFMySQLTask * mysqlTask){

    function<void(WFMySQLTask *)> func = bind(&FileUpLoad::fileuploadMysqlCallbackUserfile,this,std::placeholders::_1);

    FileUpLoadContext * context = static_cast<FileUpLoadContext*>(series_of(mysqlTask)->get_context());
    if(mysqlTask->get_state() !=WFT_STATE_SUCCESS){
        context->resp->String("mysqlTask failed");
        return;
    }

    MySQLResultCursor cursor(mysqlTask->get_resp());

    if(cursor.get_cursor_status()==MYSQL_PACKET_ERROR){
        context->resp->String("error");
        return ;
    }

    if(cursor.get_cursor_status()==MYSQL_STATUS_GET_RESULT){
        map<string,MySQLCell> row;
        cursor.fetch_row(row);
        
        if(row.size()==0){
            #ifdef DEBUG
                std::cout<<"row size = 0\n";
            #endif
            context->is_insert = true;
        }else{
            context->ufid = row["id"].as_int();
            context->is_insert = false;
        }
        string sql = "select * from file_table where hashcode = '"+context->hashCode+"';";
        WFMySQLTask * mysqltask = WFTaskFactory::create_mysql_task("mysql://root:123@localhost/newCloudDisk",3,func);
        mysqltask->get_req()->set_query(sql);
        series_of(mysqlTask)->push_back(mysqltask);
    }else{
            context->resp->String("error");           
    }

}

void FileUpLoad::fileupload_module(){
    
    function<void(WFMySQLTask *)> func = bind(&FileUpLoad::fileuploadMysqlCallback,this,std::placeholders::_1);
    
    m_server.POST("/file/upload",[func](const HttpReq* req, HttpResp* resp,SeriesWork* series){
        string username;
        string token;
        username = req->query("username");
        token = req->query("token");
        if(CryptoUtil::verify_token(token,username)){
            if (req->content_type() != MULTIPART_FORM_DATA) {
                resp->set_status(HttpStatusBadRequest);     
                return ;
            }

            FileUpLoadContext * context = new FileUpLoadContext{};

            const Form& form = req->form();
            
            for (const auto& [_, file]: form) {
                
                context->resp = nullptr;
                context->username = "";
                context->filename = "";
                context->hashCode = "";
                context->content = "";
                context->storagepath = "";
                context->is_insert = false;

                auto& [filename, filecontent] = file;

                context->resp = resp;
                context->username = username;
                context->filename = PathUtil::base(filename);
                context->hashCode = CryptoUtil::hash_content(filecontent); 
                context->content = filecontent;
                context->storagepath = LocalRootPath + context->hashCode;
                #ifdef DEBUG
                    std::cout<<context->resp<<"\n";
                    std::cout<<context->username<<"\n";
                    std::cout<<context->filename<<"\n";
                    std::cout<<context->hashCode<<"\n"; 
                    std::cout<<context->content <<"\n";
                    std::cout<<context->storagepath <<"\n";
                    std::cout<<"context storage success\n";
                #endif
                
                string sql = "select * from user_file_table where username = '"+context->username+"' and filename = '"+context->filename+"';";
                WFMySQLTask * mysqltask = WFTaskFactory::create_mysql_task("mysql://root:123@localhost/newCloudDisk",3,func);
                mysqltask->get_req()->set_query(sql);
                series->set_context(context);
                series->push_back(mysqltask);

            }

            series->set_callback([context](const SeriesWork*){
                delete context;
            });

        }else{

            resp->String("error");
        
        }
    });    
}

void FileUpLoad::start(){
    fileupload_module();
}
